package com.panfeng.xcloud.scheduler.service.impl;

import com.panfeng.xcloud.boss.provider.dto.request.OperateBotReqDTO;
import com.panfeng.xcloud.boss.provider.dto.request.QuantBotDetailDTO;
import com.panfeng.xcloud.boss.provider.dto.request.QuantBotLockDetailDTO;
import com.panfeng.xcloud.common.core.configuration.DataDictionaryConfig;
import com.panfeng.xcloud.common.core.constants.BlConstants;
import com.panfeng.xcloud.common.core.constants.RedisConstants;
import com.panfeng.xcloud.common.core.enums.*;
import com.panfeng.xcloud.common.core.exceptions.BaseBizException;
import com.panfeng.xcloud.common.core.utils.StringUtils;
import com.panfeng.xcloud.common.core.web.vo.OrderDetailResponseVO;
import com.panfeng.xcloud.common.core.web.vo.TradeResponseVO;
import com.panfeng.xcloud.scheduler.service.*;
import groovy.util.logging.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

/**
 * 风险控制类
 */
@Slf4j
@Service
public class RiskManagerServiceImpl implements IRiskManagerService {

    private static Logger log = LoggerFactory.getLogger(RiskManagerServiceImpl.class);

    @Autowired
    private DataDictionaryConfig dataDictionaryConfig;

    @Autowired
    private IQuantBotService quantBotService;

    @Autowired
    private IFeignService feignService;

    @Autowired
    private IOffSetService offSetService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private IUtilService iUtilService;

    /**
     * 执行风险控制逻辑
     */
    @Override
    public QuantBotDetailDTO manageRisk(QuantBotDetailDTO quantBotDetailDTO, String userBotKey, BigDecimal bidPrice, BigDecimal askPrice) throws Exception {
        //如果已经锁仓,则直接返回
        if (quantBotDetailDTO.getLock()) {
            return quantBotDetailDTO;
        }

        //如果已经达到最大补仓次数,则执行锁仓,并且返回
        /*quantBotDetailDTO = toJudgeRiskRate(quantBotDetailDTO, userBotKey);
        if (quantBotDetailDTO.getLock()) {
            return quantBotDetailDTO;
        }*/

        //判断市价涨跌幅速度是否超出预期,如果是则加入相当于首单金额2倍的对冲仓
        //toJudgePriceRateSpeed(quantBotDetailDTO, userBotKey, bidPrice);

        //判断当前浮盈亏状况是否该执行硬止损清仓
        //toJudgeStopLoss(quantBotDetailDTO, userBotKey, bidPrice);

        //判断当前止损次数限制风控
        //toJudgeStopLossNumLimit(quantBotDetailDTO, userBotKey);

        //解套风控,目的是降低仓位,大仓位能尽快解套的就尽快解套，当前只有快进快出模式会自动解套
        toJudgeGetOffTime(quantBotDetailDTO, userBotKey, bidPrice);

        return quantBotDetailDTO;
    }

    /**
     * 判断达到最大补仓次数则执行锁仓
     */
    private QuantBotDetailDTO toJudgeRiskRate(QuantBotDetailDTO quantBotDetailDTO, String userBotKey) throws Exception {
        //获得当前保证金率
        /*BigDecimal riskRate = feignService.getRiskRate(Integer.valueOf(quantBotDetailDTO.getExchangeType()), quantBotDetailDTO.getUserId(), quantBotDetailDTO.getContractCode(), quantBotDetailDTO.getDirection());

        log.info(">>> 用户id:{},交易所:{},交易对:{},多空方向:{},杠杆倍数:{},当前保证金率:{} <<<", quantBotDetailDTO.getUserId(), quantBotDetailDTO.getExchangeType(),
                quantBotDetailDTO.getContractCode(), quantBotDetailDTO.getDirection(), quantBotDetailDTO.getLeverRate(), riskRate);

        if (riskRate == null) {
            //清除机器人
            iUtilService.deleteBot(userBotKey, quantBotDetailDTO);
            throw new BaseBizException(ResponseStatusEnum.RISK_RATE_QUERY_ERROR);
        }*/

        //如果当前补仓次数达到了最大补仓次数且解锁次数为0,则执行锁仓
        if (Integer.valueOf(quantBotDetailDTO.getCurrentAddCount()) >= Integer.valueOf(quantBotDetailDTO.getAddCount())
                && Integer.valueOf(quantBotDetailDTO.getCurrentUnLockNum()) == 0) {
            quantBotDetailDTO = lockAuto(quantBotDetailDTO);
        }

        return quantBotDetailDTO;
    }

    /**
     * 判断市价涨跌幅速度是否超出预期,如果是则加入相当于首单金额2倍的对冲仓
     */
    private void toJudgePriceRateSpeed(QuantBotDetailDTO quantBotDetailDTO, String userBotKey, BigDecimal bidPrice) throws Exception {
        //当前市价
        BigDecimal currentPrice = bidPrice;

        //上次市价
        BigDecimal lastPrice = new BigDecimal(quantBotDetailDTO.getCurrentBidPrice());

        //当前市价相比上次市价的涨跌幅=(当前市价-上次市价)/上次市价
        BigDecimal rateSpeed = (currentPrice.subtract(lastPrice)).divide(lastPrice, 8, BigDecimal.ROUND_DOWN);

        //判断涨跌幅是否超出预期
        if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.BUY.getCode())) {
            //如果是多单,则涨跌幅小于等于多单设定止损涨跌幅则加对冲
            if (rateSpeed.compareTo(new BigDecimal(quantBotDetailDTO.getBuyStopRate())) <= 0) {
                //加入相当于2倍首单金额的对冲仓,发送相应邮件
                doOffSet(quantBotDetailDTO);
            }
        } else if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.SELL.getCode())) {
            //如果是空单,则涨跌幅大于等于空单设定止损涨跌幅则加对冲
            if (rateSpeed.compareTo(new BigDecimal(quantBotDetailDTO.getSellStopRate())) >= 0) {
                //加入相当于2倍首单金额的对冲仓,发送相应邮件
                doOffSet(quantBotDetailDTO);
            }
        } else {
            throw new BaseBizException(ResponseStatusEnum.ROBOT_DIRECTION_NOT_EXIST);
        }
    }

    /**
     * 判断当前浮盈亏状况是否该执行硬止损清仓
     */
    private void toJudgeStopLoss(QuantBotDetailDTO quantBotDetailDTO, String userBotKey, BigDecimal bidPrice) throws Exception {
        //获取当前浮盈亏
        BigDecimal floatProfit = BigDecimal.ZERO;

        //获取当前买盘价格
        BigDecimal currentPrice = bidPrice;

        if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.BUY.getCode())) {
            floatProfit = (currentPrice.subtract(new BigDecimal(quantBotDetailDTO.getAvgPrice()))).multiply(new BigDecimal(quantBotDetailDTO.getPositionNum()))
                    .setScale(6, BigDecimal.ROUND_DOWN);
        } else {
            floatProfit = (new BigDecimal(quantBotDetailDTO.getAvgPrice()).subtract(currentPrice)).multiply(new BigDecimal(quantBotDetailDTO.getPositionNum()))
                    .setScale(6, BigDecimal.ROUND_DOWN);
        }

        //浮动盈亏止损线(根据对冲线补仓次数来判断)
        BigDecimal stopLossProfit = BigDecimal.ZERO;
        if (Integer.valueOf(quantBotDetailDTO.getCurrentAddCount()) >= Integer.valueOf(quantBotDetailDTO.getTotalStopLossNumLimitAddNum())) {
            stopLossProfit = new BigDecimal(quantBotDetailDTO.getStopLossProfit()).multiply(new BigDecimal(quantBotDetailDTO.getPositionAmount()));
        } else {
            stopLossProfit = new BigDecimal(quantBotDetailDTO.getStopLossesRatio()).multiply(new BigDecimal(quantBotDetailDTO.getPositionAmount()));
        }

        //当前浮盈亏小于等于浮动盈亏止损线时,触发止损
        if (floatProfit.compareTo(stopLossProfit) <= 0) {
            //获取redis的当前止损次数数据,更新止损次数数据,判断是否需要发送邮件
            if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.BUY.getCode())) {
                String result = stringRedisTemplate.opsForValue().get(RedisConstants.BUY_STOP_LOSS_NUM);
                Integer currentNum = StringUtils.isEmpty(result) ? 0 : Integer.valueOf(result);
                currentNum++;
                //判断是否需要发送邮件
                if (currentNum == Integer.valueOf(dataDictionaryConfig.getCodeToNameMap().get(DataDictionaryTypeEnum.buy_stop_loss_limit.getCode()))) {
                    //发送触发多单止损次数限制风控的邮件
                    feignService.sendEmail("UserId:" + quantBotDetailDTO.getUserId()
                            + "  交易对:" + quantBotDetailDTO.getDestinationCoin().concat(quantBotDetailDTO.getSourceCoin())
                            + "  方向:" + quantBotDetailDTO.getDirection()
                            + "  倍数:" + quantBotDetailDTO.getLeverRate()
                            + "  触发多单止损次数限制风控!");
                }
                //更新redis的当前多单止损次数数据
                stringRedisTemplate.opsForValue().set(RedisConstants.BUY_STOP_LOSS_NUM, String.valueOf(currentNum));
            } else if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.SELL.getCode())) {
                String result = stringRedisTemplate.opsForValue().get(RedisConstants.SELL_STOP_LOSS_NUM);
                Integer currentNum = StringUtils.isEmpty(result) ? 0 : Integer.valueOf(result);
                //更新redis的当前止损次数数据
                currentNum++;
                //判断是否需要发送邮件
                if (currentNum == Integer.valueOf(dataDictionaryConfig.getCodeToNameMap().get(DataDictionaryTypeEnum.sell_stop_loss_limit.getCode()))) {
                    //发送触发空单止损次数限制的邮件
                    feignService.sendEmail("UserId:" + quantBotDetailDTO.getUserId()
                            + "  交易对:" + quantBotDetailDTO.getDestinationCoin().concat(quantBotDetailDTO.getSourceCoin())
                            + "  方向:" + quantBotDetailDTO.getDirection()
                            + "  倍数:" + quantBotDetailDTO.getLeverRate()
                            + "  触发空单止损次数限制风控!");
                }
                //更新redis的当前空单止损次数数据
                stringRedisTemplate.opsForValue().set(RedisConstants.SELL_STOP_LOSS_NUM, String.valueOf(currentNum));
            } else {
                throw new BaseBizException(ResponseStatusEnum.ROBOT_DIRECTION_NOT_EXIST);
            }

            log.info("触发硬止损，机器人结束!");
            //清仓,停止机器人,抛相应异常
            stopAndClearBot(quantBotDetailDTO, userBotKey, SystemInnerTypeEnum.STOP_LOSS_AUTO_CLEAR.code, ResponseStatusEnum.STOP_LOSSES_ERROR);
        }
    }

    /**
     * 判断当前止损次数限制风控
     */
    private void toJudgeStopLossNumLimit(QuantBotDetailDTO quantBotDetailDTO, String userBotKey) throws Exception {
        //相应仓位方向止损次数大于等于限定值,且当前交易对补仓次数达到限定值,则直接清仓,停止机器人,抛异常
        if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.BUY.getCode())) {
            String result = stringRedisTemplate.opsForValue().get(RedisConstants.BUY_STOP_LOSS_NUM);
            Integer currentNum = StringUtils.isEmpty(result) ? 0 : Integer.valueOf(result);
            if (currentNum >= Integer.valueOf(dataDictionaryConfig.getCodeToNameMap().get(DataDictionaryTypeEnum.buy_stop_loss_limit.getCode())) &&
                    Integer.valueOf(quantBotDetailDTO.getCurrentAddCount()) >= Integer.valueOf(quantBotDetailDTO.getTotalStopLossNumLimitAddNum())) {
                //清仓,停止机器人,抛相应异常
                stopAndClearBot(quantBotDetailDTO, userBotKey, SystemInnerTypeEnum.TOTAL_STOP_LOSS_NUM_LIMIT_ADD_NUM.code, ResponseStatusEnum.TOTAL_STOP_LOSS_NUM_LIMIT_ADD_NUM);
            }
        } else if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.SELL.getCode())) {
            String result = stringRedisTemplate.opsForValue().get(RedisConstants.SELL_STOP_LOSS_NUM);
            Integer currentNum = StringUtils.isEmpty(result) ? 0 : Integer.valueOf(result);
            if (currentNum >= Integer.valueOf(dataDictionaryConfig.getCodeToNameMap().get(DataDictionaryTypeEnum.sell_stop_loss_limit.getCode())) &&
                    Integer.valueOf(quantBotDetailDTO.getCurrentAddCount()) >= Integer.valueOf(quantBotDetailDTO.getTotalStopLossNumLimitAddNum())) {
                //清仓,停止机器人,抛相应异常
                stopAndClearBot(quantBotDetailDTO, userBotKey, SystemInnerTypeEnum.TOTAL_STOP_LOSS_NUM_LIMIT_ADD_NUM.code, ResponseStatusEnum.TOTAL_STOP_LOSS_NUM_LIMIT_ADD_NUM);
            }
        } else {
            throw new BaseBizException(ResponseStatusEnum.ROBOT_DIRECTION_NOT_EXIST);
        }
    }

    /**
     * 解套风控,目的是降低仓位,大仓位能尽快解套的就尽快解套，当前只有快进快出模式会自动解套
     */
    private void toJudgeGetOffTime(QuantBotDetailDTO quantBotDetailDTO, String userBotKey, BigDecimal bidPrice) throws Exception {
        //判断是否达到指定解套补仓次数且为快进快出模式
        if (Integer.valueOf(quantBotDetailDTO.getCurrentAddCount()) >= Integer.valueOf(quantBotDetailDTO.getGetOffAddPositionNum()) &&
                new BigDecimal(quantBotDetailDTO.getGetOffNeedTime()).compareTo(new BigDecimal(RunStrategyModelTypeEnum.QUICK_IN_OUT.getCode())) == 0) {
            //获取当前浮动盈亏
            BigDecimal floatProfit = BigDecimal.ZERO;
            if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.BUY.getCode())) {
                floatProfit = (bidPrice.subtract(new BigDecimal(quantBotDetailDTO.getAvgPrice()))).multiply(new BigDecimal(quantBotDetailDTO.getPositionNum())).setScale(6, BigDecimal.ROUND_DOWN);
            } else {
                floatProfit = (new BigDecimal(quantBotDetailDTO.getAvgPrice()).subtract(bidPrice)).multiply(new BigDecimal(quantBotDetailDTO.getPositionNum())).setScale(6, BigDecimal.ROUND_DOWN);
            }
            //判断当前浮动盈亏是否小于等于交易对详情中解套风控的指定解套浮动盈亏线,如果是则直接清仓,停止机器人,然后抛异常
            if (floatProfit.compareTo(new BigDecimal(quantBotDetailDTO.getGetOffProfitLine())) <= 0) {
                //清仓,停止机器人,抛相应异常
                stopAndClearBot(quantBotDetailDTO, userBotKey, SystemInnerTypeEnum.GET_OFF_TIME_EXPIRE.code, ResponseStatusEnum.GET_OFF_TIME_EXPIRE);
            }
        }
    }

    /**
     * 执行锁仓
     */
    private QuantBotDetailDTO lockAuto(QuantBotDetailDTO quantBotDetailDTO) throws Exception {
        OperateBotReqDTO operateBotReqDTO = new OperateBotReqDTO();
        operateBotReqDTO.setUserId(quantBotDetailDTO.getUserId());
        operateBotReqDTO.setOperateType(OperateTypeEnum.BUY.getCode());
        operateBotReqDTO.setSourceCoin(quantBotDetailDTO.getSourceCoin());
        operateBotReqDTO.setDestinationCoin(quantBotDetailDTO.getDestinationCoin());
        operateBotReqDTO.setExchangeType(Integer.valueOf(quantBotDetailDTO.getExchangeType()));
        operateBotReqDTO.setAmount(new BigDecimal(quantBotDetailDTO.getPositionPiece()));
        if (quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.BUY.getCode())) {
            operateBotReqDTO.setDirection(PositionSideTypeEnum.SELL.getCode());
        } else {
            operateBotReqDTO.setDirection(PositionSideTypeEnum.BUY.getCode());
        }
        operateBotReqDTO.setLeverRate(Integer.valueOf(quantBotDetailDTO.getLeverRate()));

        //下对冲单
        TradeResponseVO tradeResponseVO = feignService.trade(Integer.valueOf(quantBotDetailDTO.getExchangeType()), operateBotReqDTO, quantBotDetailDTO);

        //获取对冲单详情
        OrderDetailResponseVO orderDetail = feignService.getOrderDetail(Integer.valueOf(quantBotDetailDTO.getExchangeType()), operateBotReqDTO, tradeResponseVO);

        //构造锁仓详情对象
        QuantBotLockDetailDTO quantBotLockDetailDTO = new QuantBotLockDetailDTO();
        quantBotLockDetailDTO.setLockDestinationCoin(quantBotDetailDTO.getDestinationCoin());
        quantBotLockDetailDTO.setLockSourceCoin(quantBotDetailDTO.getSourceCoin());
        quantBotLockDetailDTO.setLockExchangeType(quantBotDetailDTO.getExchangeType());
        quantBotLockDetailDTO.setLockContractCode(orderDetail.getContractCode());
        quantBotLockDetailDTO.setLockPositionAmount(String.valueOf(orderDetail.getTradeTurnover()));
        quantBotLockDetailDTO.setLockPositionNum(String.valueOf(orderDetail.getTradeVolumeNum()));
        quantBotLockDetailDTO.setLockPositionPiece(String.valueOf(orderDetail.getTradeVolume()));
        quantBotLockDetailDTO.setLockAvgPrice(String.valueOf(orderDetail.getTradeAvgPrice()));
        quantBotLockDetailDTO.setDirection(orderDetail.getDirection());
        quantBotLockDetailDTO.setLeverRate(String.valueOf(orderDetail.getLeverRate()));

        //设置锁仓信息
        quantBotDetailDTO.setLock(true);
        quantBotDetailDTO.setQuantBotLockDetailDTO(quantBotLockDetailDTO);

        //发送自动锁仓成功信息
        feignService.sendEmail("UserId:" + operateBotReqDTO.getUserId()
                + "  交易对:" + operateBotReqDTO.getDestinationCoin().concat(operateBotReqDTO.getSourceCoin())
                + "  方向:" + operateBotReqDTO.getDirection()
                + "  倍数:" + operateBotReqDTO.getLeverRate()
                + "  触发自动锁仓!");

        return quantBotDetailDTO;
    }

    /**
     * 清仓,停止机器人,抛相应异常
     */
    private void stopAndClearBot(QuantBotDetailDTO quantBotDetailDTO, String userBotKey, int SystemInnerTypeEnumCode, ResponseStatusEnum responseStatusEnum) throws Exception {
        //执行清仓
        quantBotService.chooseClear(quantBotDetailDTO, SystemInnerTypeEnumCode);
        //清除redis中的机器人
        iUtilService.deleteBot(userBotKey, quantBotDetailDTO);
        //抛出相应异常
        throw new BaseBizException(responseStatusEnum);
    }

    private void doOffSet(QuantBotDetailDTO quantBotDetailDTO) throws Exception {
        OperateBotReqDTO operateBotReqDTO = new OperateBotReqDTO();
        operateBotReqDTO.setExchangeType(Integer.valueOf(quantBotDetailDTO.getExchangeType()));
        operateBotReqDTO.setUserId(quantBotDetailDTO.getUserId());
        operateBotReqDTO.setDestinationCoin(quantBotDetailDTO.getDestinationCoin());
        operateBotReqDTO.setSourceCoin(quantBotDetailDTO.getSourceCoin());
        operateBotReqDTO.setDirection(quantBotDetailDTO.getDirection().equals(PositionSideTypeEnum.BUY.getCode()) ? PositionSideTypeEnum.SELL.getCode() : PositionSideTypeEnum.BUY.getCode());

        //获取真实下单数
        BigDecimal realAmount = feignService.toSetOpenOrderAmount(operateBotReqDTO, new BigDecimal(quantBotDetailDTO.getInitOrderPosition()).multiply(new BigDecimal("2")));

        //去做对冲
        offSetService.doOffSetAndEmail(quantBotDetailDTO, realAmount, SystemInnerTypeEnum.PRICE_RATE_SPEED_TOO_BIG, BlConstants.OFFSET_STOP_PROFIT_RATIO, BlConstants.SPEED_FAST_OFFSET_STOP_LOSS);
    }

}
